// 版权所有2011 Go作者。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package dwarfgen

import (
	"bytes"
	"flag"
	"fmt"
	"internal/buildcfg"
	"sort"

	"cmd/compile/internal/base"
	"cmd/compile/internal/ir"
	"cmd/compile/internal/reflectdata"
	"cmd/compile/internal/ssa"
	"cmd/compile/internal/ssagen"
	"cmd/compile/internal/types"
	"cmd/internal/dwarf"
	"cmd/internal/obj"
	"cmd/internal/objabi"
	"cmd/internal/src"
)

func Info(fnsym *obj.LSym, infosym *obj.LSym, curfn interface{}) ([]dwarf.Scope, dwarf.InlCalls) {
	fn := curfn.(*ir.Func)

	if fn.Nname != nil {
		expect := fn.Linksym()
		if fnsym.ABI() == obj.ABI0 {
			expect = fn.LinksymABI(obj.ABI0)
		}
		if fnsym != expect {
			base.Fatalf("unexpected fnsym: %v != %v", fnsym, expect)
		}
	}

	// 当一个函数有两个不同的*FUNC时，此代码
	// 对于正在处理的特定*Node 
	// 是ODCLFUNC还是ONAME节点不一致。部分原因是内联函数
	// body没有ODCLFUNC节点，这是它自身的不一致性。
	// 无论如何，出于侏儒目的对两个不同节点的处理
	// 都有细微的不同，可能是以意外的方式。CL 272253合并了
	// 两个节点的Func字段，以便代码可以看到相同的*Func，无论它是
	// 持有ODCLFUNC还是ONAME。这导致
	// DWARF输出发生变化。为了保留现有的DWARF输出，并为将来的CL保留一个
	// 有意更改，当
	// fn时，此代码执行以下操作。Op==ONAME:
	// 
	// 1。不允许在createDwarfVars中使用createComplexVars。
	// 之前，ONAME无法访问该代码，
	// 因为DebugInfo仅在ODCLFUNC上设置。
	// 在ONAME案例中调用它会导致索引超出范围恐慌。
	// 
	// 2。不要填充apdecls。fn。Func。Dcl在ODCLFUNC函数中，
	// 而不是ONAME函数中。为ONAME案例填充apdecls会导致在调用createSimpleVars后填充选中的
	// createSimpleVars，然后导致循环跳过dcl中的所有条目
	// ，这意味着不会发生RecordAutoType调用。
	// 
	// 这两个调整使toolstash-cmp暂时工作。
	// 正如他们所说，决定正确的答案是未来的工作。
	// 
	// 我们可以通过查看infosym来区分旧的ODCLFUNC和ONAME 
	// 案例。名称如果为空，则DebugInfo是
	// 从（*obj.Link）调用。populateDWARF，它曾使用
	// ODCLFUNC。如果它不是空的（名称将以$abstract结尾），则从（*obj.Link）调用
	// DebugInfo。DwarfAbstractFunc，
	// 用于使用ONAME表单。
	isODCLFUNC := infosym.Name == ""

	var apdecls []*ir.Name
	// 为fn填充decls。
	if isODCLFUNC {
		for _, n := range fn.Dcl {
			if n.Op() != ir.ONAME { // 可能是OTYPE或OLITERAL 
				continue
			}
			switch n.Class {
			case ir.PAUTO:
				if !n.Used() {
					// Text==nil->生成抽象函数
					if fnsym.Func().Text != nil {
						base.Fatalf("debuginfo unused node (AllocFrame should truncate fn.Func.Dcl)")
					}
					continue
				}
			case ir.PPARAM, ir.PPARAMOUT:
			default:
				continue
			}
			apdecls = append(apdecls, n)
			fnsym.Func().RecordAutoType(reflectdata.TypeLinksym(n.Type()))
		}
	}

	decls, dwarfVars := createDwarfVars(fnsym, isODCLFUNC, fn, apdecls)

	// 对于自动变量引用的每个类型，而不是已被矮化变量引用的
	// 类型，将R_USETYPE重新定位附加到
	// 函数符号，以确保在链接过程中DWARF 
	// 处理中包含的类型。
	typesyms := []*obj.LSym{}
	for t, _ := range fnsym.Func().Autot {
		typesyms = append(typesyms, t)
	}
	sort.Sort(obj.BySymName(typesyms))
	for _, sym := range typesyms {
		r := obj.Addrel(infosym)
		r.Sym = sym
		r.Type = objabi.R_USETYPE
	}
	fnsym.Func().Autot = nil

	var varScopes []ir.ScopeID
	for _, decl := range decls {
		pos := declPos(decl)
		varScopes = append(varScopes, findScope(fn.Marks, pos))
	}

	scopes := assembleScopes(fnsym, fn, dwarfVars, varScopes)
	var inlcalls dwarf.InlCalls
	if base.Flag.GenDwarfInl > 0 {
		inlcalls = assembleInlines(fnsym, dwarfVars)
	}
	return scopes, inlcalls
}

func declPos(decl *ir.Name) src.XPos {
	return decl.Canonical().Pos()
}

// createDwarfVars进程fn，返回一个矮变量列表以及它们所代表的节点。
func createDwarfVars(fnsym *obj.LSym, complexOK bool, fn *ir.Func, apDecls []*ir.Name) ([]*ir.Name, []*dwarf.Var) {
	// 收集矮人变形金刚的原始列表。
	var vars []*dwarf.Var
	var decls []*ir.Name
	var selected ir.NameSet

	if base.Ctxt.Flag_locationlists && base.Ctxt.Flag_optimize && fn.DebugInfo != nil && complexOK {
		decls, vars, selected = createComplexVars(fnsym, fn)
	} else if fn.ABI == obj.ABIInternal && base.Flag.N != 0 && complexOK {
		decls, vars, selected = createABIVars(fnsym, fn, apDecls)
	} else {
		decls, vars, selected = createSimpleVars(fnsym, apDecls)
	}

	dcl := apDecls
	if fnsym.WasInlined() {
		dcl = preInliningDcls(fnsym)
	} else {
		// 后端的stackframe pass从
		// fn的Dcl列表中删除条目，包括对应于
		// 在寄存器中传递的输出参数的PARAMOUT节点。在这里添加这些
		// 条目，以便我们可以在
		// DWARF-gen期间正确处理它们。有关更多详细信息，请参阅第48573期。
		debugInfo := fn.DebugInfo.(*ssa.FuncDebug)
		for _, n := range debugInfo.RegOutputParams {
			if n.Class != ir.PPARAMOUT || !n.IsOutputParamInRegisters() {
				panic("invalid ir.Name on debugInfo.RegOutputParams list")
			}
			dcl = append(dcl, n)
		}
	}

	// 如果启用了优化，上面的列表通常是
	// 缺少
	// 函数中的一些原始预优化变量（它们可能已升级为寄存器，折叠为
	// 常量，死编码等）。也缺少不符合SSA优化条件的输入参数
	// 。在这里，我们在
	// 条目中重新添加所选缺失变量。请注意，下面的配方创建了
	// 保守位置。这里的想法是，我们想告诉用户“是的，这个函数中有一个名为X 
	// 的变量，但是没有，我没有足够的信息来可靠地报告它的内容。”
	// 对于不支持SSA的参数，正确的信息
	// 是已知的——它们在堆栈上有一个主元素。
	for _, n := range dcl {
		if selected.Has(n) {
			continue
		}
		c := n.Sym().Name[0]
		if c == '.' || n.Type().IsUntyped() {
			continue
		}
		if n.Class == ir.PPARAM && !ssagen.TypeOK(n.Type()) {
			// 支持SSA的参数会得到位置列表，并且可能会移入寄存器，而
			// 移出寄存器，所以这些都会在其他地方处理。
			// 自动和命名输出参数似乎通过创建位置列表的VARDEF得到处理。
			// 此处处理不属于SSA-able类型的参数；他们反对
			// 被放在堆栈的一个地方，用于
			// 整个调用。
			vars = append(vars, createSimpleVar(fnsym, n))
			decls = append(decls, n)
			continue
		}
		typename := dwarf.InfoPrefix + types.TypeSymName(n.Type())
		decls = append(decls, n)
		abbrev := dwarf.DW_ABRV_AUTO_LOCLIST
		isReturnValue := (n.Class == ir.PPARAMOUT)
		if n.Class == ir.PPARAM || n.Class == ir.PPARAMOUT {
			abbrev = dwarf.DW_ABRV_PARAM_LOCLIST
		}
		if n.Esc() == ir.EscHeap {
			// 相关变量已升级到堆中。
			// 其地址在n.HEAPADR。
			// TODO（thanm）：生成一个更好的位置表达式
		}
		inlIndex := 0
		if base.Flag.GenDwarfInl > 1 {
			if n.InlFormal() || n.InlLocal() {
				inlIndex = posInlIndex(n.Pos()) + 1
				if n.InlFormal() {
					abbrev = dwarf.DW_ABRV_PARAM_LOCLIST
				}
			}
		}
		declpos := base.Ctxt.InnermostPos(n.Pos())
		vars = append(vars, &dwarf.Var{
			Name:          n.Sym().Name,
			IsReturnValue: isReturnValue,
			Abbrev:        abbrev,
			StackOffset:   int32(n.FrameOffset()),
			Type:          base.Ctxt.Lookup(typename),
			DeclFile:      declpos.RelFilename(),
			DeclLine:      declpos.RelLine(),
			DeclCol:       declpos.RelCol(),
			InlIndex:      int32(inlIndex),
			ChildIndex:    -1,
			DictIndex:     n.DictIndex,
		})
		// 记录go类型，以确保它由链接器发出。
		fnsym.Func().RecordAutoType(reflectdata.TypeLinksym(n.Type()))
	}

	// 对数据和变量进行排序。
	sortDeclsAndVars(fn, decls, vars)

	return decls, vars
}

// sortDeclsAndVars根据
// 参数声明顺序对decl和dwarf var列表进行排序，以确保在发出子程序
// DIE时，其参数子项按声明顺序出现。
// 在寄存器ABI出现之前，按帧偏移量排序
// 可以实现这一点；有了寄存器，我们现在需要回到
// 原始函数签名。
func sortDeclsAndVars(fn *ir.Func, decls []*ir.Name, vars []*dwarf.Var) {
	paramOrder := make(map[*ir.Name]int)
	idx := 1
	for _, selfn := range types.RecvsParamsResults {
		fsl := selfn(fn.Type()).FieldSlice()
		for _, f := range fsl {
			if n, ok := f.Nname.(*ir.Name); ok {
				paramOrder[n] = idx
				idx++
			}
		}
	}
	sort.Stable(varsAndDecls{decls, vars, paramOrder})
}

type varsAndDecls struct {
	decls      []*ir.Name
	vars       []*dwarf.Var
	paramOrder map[*ir.Name]int
}

func (v varsAndDecls) Len() int {
	return len(v.decls)
}

func (v varsAndDecls) Less(i, j int) bool {
	nameLT := func(ni, nj *ir.Name) bool {
		oi, foundi := v.paramOrder[ni]
		oj, foundj := v.paramOrder[nj]
		if foundi {
			if foundj {
				return oi < oj
			} else {
				return true
			}
		}
		return false
	}
	return nameLT(v.decls[i], v.decls[j])
}

func (v varsAndDecls) Swap(i, j int) {
	v.vars[i], v.vars[j] = v.vars[j], v.vars[i]
	v.decls[i], v.decls[j] = v.decls[j], v.decls[i]
}

// 给定在编译过程中某个时间点内联的函数，在内联之前返回该函数中与
// autos/locals对应的节点的排序列表。如果这是一个
// 函数，它不是正在编译的包的本地函数，那么
// 变量的名称可能已经“版本化”，以避免
// 与本地变量冲突；排序时忽略此版本控制。
func preInliningDcls(fnsym *obj.LSym) []*ir.Name {
	fn := base.Ctxt.DwFixups.GetPrecursorFunc(fnsym).(*ir.Func)
	var rdcl []*ir.Name
	for _, n := range fn.Inl.Dcl {
		c := n.Sym().Name[0]
		// 避免报告“”参数，因为如果有多个
		// 参数，可能会在以后导致冲突，如#23179。
		if unversion(n.Sym().Name) == "_" || c == '.' || n.Type().IsUntyped() {
			continue
		}
		rdcl = append(rdcl, n)
	}
	return rdcl
}

// createSimpleVars为
// 函数中声明的每个变量创建一个侏儒条目，声称它们永久在堆栈上。
func createSimpleVars(fnsym *obj.LSym, apDecls []*ir.Name) ([]*ir.Name, []*dwarf.Var, ir.NameSet) {
	var vars []*dwarf.Var
	var decls []*ir.Name
	var selected ir.NameSet
	for _, n := range apDecls {
		if ir.IsAutoTmp(n) {
			continue
		}

		decls = append(decls, n)
		vars = append(vars, createSimpleVar(fnsym, n))
		selected.Add(n)
	}
	return decls, vars, selected
}

func createSimpleVar(fnsym *obj.LSym, n *ir.Name) *dwarf.Var {
	var abbrev int
	var offs int64

	localAutoOffset := func() int64 {
		offs = n.FrameOffset()
		if base.Ctxt.FixedFrameSize() == 0 {
			offs -= int64(types.PtrSize)
		}
		if buildcfg.FramePointerEnabled {
			offs -= int64(types.PtrSize)
		}
		return offs
	}

	switch n.Class {
	case ir.PAUTO:
		offs = localAutoOffset()
		abbrev = dwarf.DW_ABRV_AUTO
	case ir.PPARAM, ir.PPARAMOUT:
		abbrev = dwarf.DW_ABRV_PARAM
		if n.IsOutputParamInRegisters() {
			offs = localAutoOffset()
		} else {
			offs = n.FrameOffset() + base.Ctxt.FixedFrameSize()
		}

	default:
		base.Fatalf("createSimpleVar unexpected class %v for node %v", n.Class, n)
	}

	typename := dwarf.InfoPrefix + types.TypeSymName(n.Type())
	delete(fnsym.Func().Autot, reflectdata.TypeLinksym(n.Type()))
	inlIndex := 0
	if base.Flag.GenDwarfInl > 1 {
		if n.InlFormal() || n.InlLocal() {
			inlIndex = posInlIndex(n.Pos()) + 1
			if n.InlFormal() {
				abbrev = dwarf.DW_ABRV_PARAM
			}
		}
	}
	declpos := base.Ctxt.InnermostPos(declPos(n))
	return &dwarf.Var{
		Name:          n.Sym().Name,
		IsReturnValue: n.Class == ir.PPARAMOUT,
		IsInlFormal:   n.InlFormal(),
		Abbrev:        abbrev,
		StackOffset:   int32(offs),
		Type:          base.Ctxt.Lookup(typename),
		DeclFile:      declpos.RelFilename(),
		DeclLine:      declpos.RelLine(),
		DeclCol:       declpos.RelCol(),
		InlIndex:      int32(inlIndex),
		ChildIndex:    -1,
		DictIndex:     n.DictIndex,
	}
}

// createABIVars为启用
// 寄存器ABI但关闭优化的函数创建矮化变量。它使用
// 混合方法，其中寄存器驻留输入参数是
// 用位置列表捕获，所有其他变量使用“简单”
// 策略。
func createABIVars(fnsym *obj.LSym, fn *ir.Func, apDecls []*ir.Name) ([]*ir.Name, []*dwarf.Var, ir.NameSet) {

	// 调用createComplexVars为输入参数
	// 生成矮化变量，这些参数是根据ABI规则进行寄存器分配的。
	decls, vars, selected := createComplexVars(fnsym, fn)

	// 现在填写其余变量：输入参数
	// 不是寄存器驻留的、输出参数和本地
	// 变量。
	for _, n := range apDecls {
		if ir.IsAutoTmp(n) {
			continue
		}
		if _, ok := selected[n]; ok {
			// 已处理
			continue
		}

		decls = append(decls, n)
		vars = append(vars, createSimpleVar(fnsym, n))
		selected.Add(n)
	}

	return decls, vars, selected
}

// createComplexVars创建带位置列表的重新组合矮化变量，
// 适合描述优化代码。
func createComplexVars(fnsym *obj.LSym, fn *ir.Func) ([]*ir.Name, []*dwarf.Var, ir.NameSet) {
	debugInfo := fn.DebugInfo.(*ssa.FuncDebug)

	// 为每个用户变量生成一个侏儒变量条目。
	var decls []*ir.Name
	var vars []*dwarf.Var
	var ssaVars ir.NameSet

	for varID, dvar := range debugInfo.Vars {
		n := dvar
		ssaVars.Add(n)
		for _, slot := range debugInfo.VarSlots[varID] {
			ssaVars.Add(debugInfo.Slots[slot].N)
		}

		if dvar := createComplexVar(fnsym, fn, ssa.VarID(varID)); dvar != nil {
			decls = append(decls, n)
			vars = append(vars, dvar)
		}
	}

	return decls, vars, ssaVars
}

// createComplexVar构建一个DWARF变量条目和位置列表。
func createComplexVar(fnsym *obj.LSym, fn *ir.Func, varID ssa.VarID) *dwarf.Var {
	debug := fn.DebugInfo.(*ssa.FuncDebug)
	n := debug.Vars[varID]

	var abbrev int
	switch n.Class {
	case ir.PAUTO:
		abbrev = dwarf.DW_ABRV_AUTO_LOCLIST
	case ir.PPARAM, ir.PPARAMOUT:
		abbrev = dwarf.DW_ABRV_PARAM_LOCLIST
	default:
		return nil
	}

	gotype := reflectdata.TypeLinksym(n.Type())
	delete(fnsym.Func().Autot, gotype)
	typename := dwarf.InfoPrefix + gotype.Name[len("type."):]
	inlIndex := 0
	if base.Flag.GenDwarfInl > 1 {
		if n.InlFormal() || n.InlLocal() {
			inlIndex = posInlIndex(n.Pos()) + 1
			if n.InlFormal() {
				abbrev = dwarf.DW_ABRV_PARAM_LOCLIST
			}
		}
	}
	declpos := base.Ctxt.InnermostPos(n.Pos())
	dvar := &dwarf.Var{
		Name:          n.Sym().Name,
		IsReturnValue: n.Class == ir.PPARAMOUT,
		IsInlFormal:   n.InlFormal(),
		Abbrev:        abbrev,
		Type:          base.Ctxt.Lookup(typename),
		// 堆栈偏移量用作排序键，因此对于分解的
		// 变量，只需给出第一个。否则就不用了。
		// 如果没有为第一个插槽分配堆栈
		// 位置，这将无法正常工作，但如何做得更好并不明显。
		StackOffset: ssagen.StackOffset(debug.Slots[debug.VarSlots[varID][0]]),
		DeclFile:    declpos.RelFilename(),
		DeclLine:    declpos.RelLine(),
		DeclCol:     declpos.RelCol(),
		InlIndex:    int32(inlIndex),
		ChildIndex:  -1,
		DictIndex:   n.DictIndex,
	}
	list := debug.LocationLists[varID]
	if len(list) != 0 {
		dvar.PutLocationList = func(listSym, startPC dwarf.Sym) {
			debug.PutLocationList(list, base.Ctxt, listSym.(*obj.LSym), startPC.(*obj.LSym))
		}
	}
	return dvar
}

// RecordFlags记录要放置在DWARF信息中的指定命令行标志。
func RecordFlags(flags ...string) {
	if base.Ctxt.Pkgpath == "" {
		// 如果我们不知道
		// 包名是什么，我们就不能记录这些标志。
		return
	}

	type BoolFlag interface {
		IsBoolFlag() bool
	}
	type CountFlag interface {
		IsCountFlag() bool
	}
	var cmd bytes.Buffer
	for _, name := range flags {
		f := flag.Lookup(name)
		if f == nil {
			continue
		}
		getter := f.Value.(flag.Getter)
		if getter.String() == f.DefValue {
			// 标志有默认值，所以省略它。
			continue
		}
		if bf, ok := f.Value.(BoolFlag); ok && bf.IsBoolFlag() {
			val, ok := getter.Get().(bool)
			if ok && val {
				fmt.Fprintf(&cmd, " -%s", f.Name)
				continue
			}
		}
		if cf, ok := f.Value.(CountFlag); ok && cf.IsCountFlag() {
			val, ok := getter.Get().(int)
			if ok && val == 1 {
				fmt.Fprintf(&cmd, " -%s", f.Name)
				continue
			}
		}
		fmt.Fprintf(&cmd, " -%s=%v", f.Name, getter.Get())
	}

	// 在生产者字符串中添加标志，以指示regabi是开启还是关闭。
	// 一旦全面开启regabi并且相关的GOEXPERIMENT 
	// 旋钮不再存在，则应删除此代码。
	if buildcfg.Experiment.RegabiArgs {
		cmd.Write([]byte(" regabi"))
	}

	if cmd.Len() == 0 {
		return
	}
	s := base.Ctxt.Lookup(dwarf.CUInfoPrefix + "producer." + base.Ctxt.Pkgpath)
	s.Type = objabi.SDWARFCUINFO
	// 有时（例如在构建测试时），我们可以将两个包主档案链接在一起。所以允许重复。
	s.Set(obj.AttrDuplicateOK, true)
	base.Ctxt.Data = append(base.Ctxt.Data, s)
	s.P = cmd.Bytes()[1:]
}

// RecordPackageName记录正在编译的包的名称，以便链接器可以将其保存在编译单元的骰子中。
func RecordPackageName() {
	s := base.Ctxt.Lookup(dwarf.CUInfoPrefix + "packagename." + base.Ctxt.Pkgpath)
	s.Type = objabi.SDWARFCUINFO
	// 有时（例如在构建测试时），我们可以将两个包主档案链接在一起。所以允许重复。
	s.Set(obj.AttrDuplicateOK, true)
	base.Ctxt.Data = append(base.Ctxt.Data, s)
	s.P = []byte(types.LocalPkg.Name)
}
